/*
Hermes Core: General Purpose Mobile Agent Platform
Copyright (C) 2006 UNICAM - Università degli Studi di Camerino

Group leaders: Flavio Corradini and Emanuela Merelli

Developers: Ezio Bartocci, Diego Bonura, Rosario Culmone,
Davide Lorenzoli, Leonardo Mariani, Francesca Piersigilli, 
Lorenzo Scortichini, Leonardo Vito.

GNU Lesser Public License

This library is free software; you can redistribute it 
and/or modify it under the terms of the GNU Lesser General 
Public License as published by the Free Software Foundation, 
version 2.1 of the License.

This library is distributed in the hope that it will be useful, 
but WITHOUT ANY WARRANTY; without even the implied warranty of 
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
See the GNU Library General Public License for more details.

You should have received a copy of the GNU Library General Public 
License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.

Emanuela Merelli
Dipartimento di Matematica ed Informatica
Università di Camerino
via Madonna delle Carceri, 62032 Camerino, Italy
E.Mail: emanuela.merelli@unicam.it
*/
package hermesV2.basicServices.genesis;
import hermesV2.Envelope;
import hermesV2.PlaceAddress;
import hermesV2.agent.Agent;
import hermesV2.agent.Identificator;
import hermesV2.agent.UserAgent;
import hermesV2.core.id.Identify;
import hermesV2.core.id.Trace;
import hermesV2.core.net.EnvelopeIO;
import hermesV2.core.starter.Starter;
import hermesV2.util.HermesLogger;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
/**
 * This is the component that permits to clone agents. It has also the
 * responsability to communicate to places when an agent dies.
 * 
 * @author Diego Bonura
 */
public class GenesisManager implements Genesis {
	/** This is the starter interface of the starter interface. */
	private Starter starter;
	/**
	 * This is the interface of the net component to send and to receive
	 * envelope.
	 */
	private EnvelopeIO envelopeIO;
	/** This is the identify interface of the id component. */
	private Identify identify;
	/** This is the trae interface of the id component. */
	private Trace trace;
	/** The Place address of the current place. */
	private PlaceAddress currentPlaceAddress;
	/**
	 * Creates a new GenesisManager object.
	 * 
	 * @param currentPlaceAddress
	 *            The current place address.
	 */
	public GenesisManager(PlaceAddress currentPlaceAddress) {
		this.currentPlaceAddress = currentPlaceAddress;
	}
	/**
	 * Sets the starter interface into the Genesis component.
	 * 
	 * @param starter
	 *            The starter interface of the starter component.
	 */
	public void setStarter(Starter starter) {
		this.starter = starter;
	}
	/**
	 * Sets the envelope interface into the Genesis component.
	 * 
	 * @param envelopeIO
	 *            The Envelope interface of the net component.
	 */
	public void setEnvelopeIO(EnvelopeIO envelopeIO) {
		this.envelopeIO = envelopeIO;
	}
	/**
	 * Sets the identify interface into the Genesis component.
	 * 
	 * @param identify
	 *            The Identify interface of the Id component.
	 */
	public void setIdentify(Identify identify) {
		this.identify = identify;
	}
	/**
	 * Sets the Trace interface into the Genesis component.
	 * 
	 * @param trace
	 *            The trace interface of the Id component.
	 */
	public void setTrace(Trace trace) {
		this.trace = trace;
	}
	/**
	 * This method permits the clonation of an user agent. It handles the
	 * creation of a new identificator and the starting of the cloned agent.
	 * 
	 * @param anAgent
	 *            The agent to clone.
	 * @param agentName
	 * 			  The name of the cloned agent.
	 * 
	 * 
	 * @return The identificator of the cloned agent.
	 */
	public Identificator cloneAgent(UserAgent anAgent,String agentName) {
		UserAgent son = null;
		Identificator sonId;
		if (starter != null) {
			try {
				ByteArrayOutputStream baos = new ByteArrayOutputStream();
				ObjectOutputStream oos = new ObjectOutputStream(baos);
				oos.writeObject(anAgent);
				ByteArrayInputStream bais = new ByteArrayInputStream(baos
						.toByteArray());
				GenesisObjectInputStream gois = new GenesisObjectInputStream(bais);
				gois.setLoader(anAgent.getClass().getClassLoader());
				//ObjectInputStream ois = new ObjectInputStream(bais);
				son = (UserAgent) gois.readObject();
				son.setAgentName(agentName);
				son.setFatherIdentificator(anAgent.getIdentificator());
				starter.startAgent(son, UserAgent.CLONE, false);
				sonId = son.getIdentificator();
			} catch (IOException ioe) {
				HermesLogger.log(HermesLogger.WARNING, "Agent: "
						+ anAgent.getIdentificator()
						+ "Exception DeepClone EIOException", ioe);
				sonId = null;
			} catch (ClassNotFoundException cnfe) {
				HermesLogger.log(HermesLogger.WARNING, "Agent: "
						+ anAgent.getIdentificator()
						+ "Exception DeepClone ClassNotFoundException", cnfe);
				sonId = null;
			}
		} else {
			HermesLogger.log(HermesLogger.WARNING, "Genesis interface null!");
			sonId = null;
		}
		return sonId;
	}
	
	/**
	 * It creates a new agent giving the class of an agent.
	 * 
	 * @param agentClass
	 *            The class of the agent.
	 * @param agentName
	 * 			  The name of the new agent
	 * 
	 * @return The identificator of the new agent.
	 */
	public Identificator createAgent(Class<?> agentClass, String agentName) {
		Object[] objects =new Object[]{agentName};
	    return createAgent(agentClass,objects);
	}
	
	
	/**
	 * It creates a new agent giving the class of an agent.
	 * 
	 * @param agentClass
	 *            The class of the agent.
	 * @param objects
	 * 			  The parameters of the new agent
	 * 
	 * @return The identificator of the new agent.
	 */
	public Identificator createAgent(Class<?> agentClass, Object[] objects) {
		Identificator newId;
		
		
		
		if (isValid(agentClass)
				&& objects!=null) {
			//Get the agent object
			try {
			    Class<?>[] classes = new Class[objects.length];
			    for (int i=0;i<objects.length;i++){
			        classes[i]=objects[i].getClass();
			    }
				UserAgent objAgent = (UserAgent)agentClass
						.getConstructor(classes)
						.newInstance(objects);
				starter.startAgent(objAgent, Agent.INIT, false);
				newId = objAgent.getIdentificator();
			} catch (IllegalAccessException iae) {
				newId = null;
				HermesLogger.log(HermesLogger.WARNING,
						"IllegalAccessException", iae);
			} catch (InvocationTargetException ite) {
				newId = null;
				HermesLogger.log(HermesLogger.WARNING,
						"InvocationTargetException", ite);
			} catch (NoSuchMethodException nsme) {
				newId = null;
				HermesLogger.log(HermesLogger.WARNING, "NoSuchMethodException",
						nsme);
			} catch (InstantiationException ie) {
				newId = null;
				HermesLogger.log(HermesLogger.WARNING,
						"InstantiationException", ie);
			}
			//Get the agent Identificator
		} else {
			newId = null;
			HermesLogger.log(HermesLogger.WARNING,
					"General error during agent creation.");
		}
		return newId;
	}
	
	
	/**
	 * This method is automatically called when an agent dies. It cleans every
	 * hashtables into the current place and it erases the entry into the born
	 * place. It uses the EnvelopeIO interface to communicate with remote
	 * places.
	 * 
	 * @param agentId
	 *            The identificator of the agent died.
	 */
	public void destroyAgent(Identificator agentId) {
		//I have to delete the record in the current agent hashtable and in the
		// remote born hashtable
		//First i have to erase the local agent hashtable by the Identify
		// component.
		if (identify != null) {
			identify.eraseAgentIdentificator(agentId);
			//Second i have to erase the record in the remote born agent
			// hashtable
			// The agent is born in the current Place?
			if (agentId.getBornPlaceAddress().toString().equals(
					currentPlaceAddress.toString())) {
				//I have to erase the record locally
				if (trace.eraseAgentAddress(agentId)) {
					HermesLogger.log(HermesLogger.DEBUG, "Agent: " + agentId
							+ " identificator erased locally");
				} else {
					HermesLogger
							.log(
									HermesLogger.WARNING,
									"Agent: "
											+ agentId
											+ "The local place dosen't erase the bornsTable entry of the agent");
				}
			} else {
				//The current agent is born in a remote place
				//I'm going to send a special message to the remote place
				Envelope<?> reply = null;
				if (envelopeIO != null) {
					try {
						reply = envelopeIO.sendEnvelope(new Envelope<Identificator>(
								Envelope.ADDRESS_ERASE_REQUEST, agentId,
								currentPlaceAddress, agentId
										.getBornPlaceAddress()));
					} catch (IOException ioe) {
						HermesLogger.log(HermesLogger.WARNING, "IOException",
								ioe);
					}
					if (reply != null && reply.getType() == Envelope.RECEPTION_KO) {
						HermesLogger
								.log(
										HermesLogger.WARNING,
										"Agent: "
												+ agentId
												+ "The remote place: "
												+ agentId.getBornPlaceAddress()
												+ " dosen't erase the bornsTable entry of the agent");
					} else {
						HermesLogger.log(HermesLogger.DEBUG, "Agent: " + agentId
								+ "The remote place: "
								+ agentId.getBornPlaceAddress()
								+ " erased the entry into the bornsTable");
					}
				} else {
					HermesLogger
							.log(HermesLogger.WARNING,
									" The EnvelopeIO inteface of the Net component is null!");
				}
			}
		} else {
			HermesLogger.log(HermesLogger.WARNING,
					"The Identify interface of the Id component is null!");
		}
	}
	
	
	private boolean isValid(Class<?> agentClass){
	    Class<?> temp = agentClass;
		boolean valid=false;
		while(!valid && temp!=null){
		    temp = temp.getSuperclass();
		    if (temp!=null){
		    if (temp.equals(UserAgent.class)){
		        valid =true;
		    }}
		}
		return valid;
	}
}